﻿/******************************************************************************/
#include "stdafx.h"
#include "Main.h"
/******************************************************************************/
struct AttackType
{
   I8      side;
   CChar8 *anim;
}
AttackLeftHand[]=
{
   {-1, "anim/swing/l-l.anim" },
   {+1, "anim/swing/l-r.anim" },
   {-1, "anim/swing/l-ld.anim"},
   {+1, "anim/swing/l-rd.anim"},
},
AttackRightHand[]=
{
   {-1, "anim/swing/r-l.anim" },
   {+1, "anim/swing/r-r.anim" },
   {-1, "anim/swing/r-ld.anim"},
   {+1, "anim/swing/r-rd.anim"},
};
/******************************************************************************/
// ATTACK
/******************************************************************************/
void Attack::set(CSkeleton &cskel, Str animation, Bool left_hand, Int side)
{
   super::set(cskel, animation);
   T.left_hand=left_hand;
   T.side     =side;
}
void Attack::save(File &f)
{
   super::save(f);
   f<<left_hand<<side;
}
Bool Attack::load(File &f, CSkeleton &cskel)
{
   if(super::load(f, cskel))
   {
      f>>left_hand>>side;
      return true;
   }
   return false;
}
/******************************************************************************/
// CHR
/******************************************************************************/
Chr::Chr()
{
   action=ACTION_NONE;
}
/******************************************************************************/
// GET
/******************************************************************************/
Bool Chr::inRange(Obj *target)
{
   if(target)
   {
      if(Chr *chr=CAST(Chr, target))return Dist(pos(), chr->pos()) <= 1.2f+ctrl.radius()+chr->ctrl.radius();
   }
   return false;
}
/******************************************************************************/
// ACTIONS
/******************************************************************************/
void Chr::actionBreak()
{
   super::actionBreak();
   action_target.clear();
   action=ACTION_NONE;
   action_update_time=0;
}
/******************************************************************************/
Bool Chr::actionMoveTo(C Vec &pos)
{
   actionBreak();

   if(super::actionMoveTo(pos))
   {
      action=ACTION_MOVE_TO;
      return true;
   }
   return false;
}
/******************************************************************************/
void Chr::actionAttack(Chr &chr)
{
   actionBreak();
   
   action_target=chr;
   action=ACTION_ATTACK;
}
/******************************************************************************/
void Chr::attackLeftHand()
{
   if(!attack_next.is())
   {
      AttackType *attack_type=&AttackLeftHand[Random(Elms(AttackLeftHand))]; // take a random attack type

      REP(3) // 3 attempts
         if(attack_type->side==(attack.is() ? attack.side : attack_prev.side)) // if side of new attack is the same as previous attack
            attack_type=&AttackLeftHand[Random(Elms(AttackLeftHand))]; // take another random attack to minimize attacking always from the same side

      attack_next.set(cskel, attack_type->anim, true, attack_type->side);
   }
}
/******************************************************************************/
void Chr::attackRightHand()
{
   if(!attack_next.is())
   {
      AttackType *attack_type=&AttackRightHand[Random(Elms(AttackLeftHand))]; // take a random attack type

      REP(3) // 3 attempts
         if(attack_type->side==(attack.is() ? attack.side : attack_prev.side)) // if side of new attack is the same as previous attack
            attack_type=&AttackRightHand[Random(Elms(AttackLeftHand))]; // take another random attack to minimize attacking always from the same side

      attack_next.set(cskel, attack_type->anim, false, attack_type->side);
   }
}
/******************************************************************************/
// ANIMATE
/******************************************************************************/
void Chr::playAnimation(Str anim)
{
   if(motions.New().set(cskel, anim).is())
      sendAnimation(motions.last().skel_anim);
}
void Chr::animate()
{
   super::animate();
                cskel.animate(attack_prev, true);
                cskel.animate(attack     , true);
   REPA(motions)cskel.animate(motions[i] , true);
}
/******************************************************************************/
// UPDATE
/******************************************************************************/
void Chr::updateActions()
{
   switch(action)
   {
      case ACTION_MOVE_TO:
      {
         if(super::action != Game::ACTION_MOVE_TO)actionBreak(); // reached target
      }break;

      case ACTION_ATTACK:
      {
         if(!action_target.valid())actionBreak();else
         {
            Obj &obj=action_target();
            Chr *chr=CAST(Chr, &obj);

            if(inRange(chr)) // in attack range
            {
               moveStop();

               // rotate to face the target
               Flt angle_delta    =AngleDelta(angle.x+PI_2, Angle(action_target().pos().xz() - pos().xz())), // calculate angle delta between current look angle and target angle
                   max_frame_delta=Time.d()*DegToRad(270); // 270 degrees per second, this is the maximum angle delta limit to change per frame

               Clamp(angle_delta, -max_frame_delta, max_frame_delta);
               angle.x+=angle_delta;

               if(!attack.is())attackRightHand();
            }
            else // move to target
            {
                  action_update_time-=Time.d();
               if(action_update_time<=0)
               {
                  if(!moveTo(obj.pos(), true))actionBreak();else action_update_time=RandomF(0.05f, 0.1f);
               }
            }
         }
      }break;
   }
}
/******************************************************************************/
void Chr::updateAttacks()
{
   if(attack.is())
   {
      Bool break_attack=(attack.updateIn(4, 1)==false);

      // check if the attack hit anything
      if(attack.eventBetween("hit-from", "hit-to"))
      {
         // check for collision with other players and add damage
      }

      if(break_attack)
      {
         attack_prev.clear(); Swap(attack, attack_prev);
      }
   }
   attack_prev.updateOut(2);
   if(!attack.is() && attack_next.is())
   {
      Swap         (attack, attack_next);
      sendAnimation(attack.skel_anim   );
   }
}
/******************************************************************************/
Bool Chr::update()
{
   REPA(motions)if(!motions[i].updateAuto(3, 3))motions.remove(i, true);
   ctrl_pos=ctrl.center();
   if(super::update())
   {
      updateActions();
      updateAttacks();
      inv.update(cskel, this);
      return true;
   }
   return false;
}
/******************************************************************************/
// DRAW
/******************************************************************************/
UInt Chr::drawPrepare()
{
   if(mesh)if(Frustum(Ball().setAnimated(mesh->box, cskel)))
   {
      inv.drawPrepare();

      MaterialLock=material(); mesh->draw(cskel);
      MaterialLock=NULL      ;

      return IndexToFlag(RM_BEHIND); // draw mesh also in behind mode
   }
   return 0; // no additional render modes required
}
void Chr::drawShadow()
{
   if(mesh)if(Frustum(Ball().setAnimated(mesh->box, cskel)))
   {
      inv.drawShadow();

      MaterialLock=material(); mesh->drawShadow(cskel);
      MaterialLock=NULL      ;
   }
}
void Chr::drawBehind()
{
   if(mesh)
   {
      SetBehindBias(mesh->box.h()*cskel.scale());
      mesh->drawBehind(Color(64,128,255,255), Color(255,255,255,0), cskel);
   }
}
/******************************************************************************/
// PRIVATE
/******************************************************************************/
Bool Chr::moveTo(C Vec &pos, Bool allow_near)
{
   // first try finding path directly to 'pos' location
   if(super::actionMoveTo(pos))return true;

   // if it fails try finding path to positions near 'pos'
   if(allow_near)
      for(Int r= 1; r<=3; r++) // radius
      for(Int x=-1; x<=1; x++) // x offset
      for(Int z=-1; z<=1; z++) // z offset
         if(x || z)
            if(super::actionMoveTo(pos+Vec(x,0,z)*(r*0.6f)))return true;

   return false;
}
void Chr::moveStop()
{
   super::actionBreak();
}
/******************************************************************************/
